﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.IO;
using Tesla.Core;
using Tesla.Graphics;
using Tesla.Math;

namespace Tesla.Content.Loaders {
    /// <summary>
    /// Parser for a Tesla Engine Material (TEM) script file. Loads either the first material in the file, or a material collection.
    /// </summary>
    public sealed class MaterialParser {
        private StringTokenizer _reader;
        private String _fullResourceName;
        private ContentManager _content;

        //Render state cache, so we can re-use the same renderstates between material instances
        private Dictionary<RenderStateKey, RenderState>[] _cachedRenderStates;

        //Per-material data
        private Dictionary<String, MaterialParameter> _matParams;
        private Dictionary<String, MaterialEngineParameter> _engineParams;
        private Dictionary<String, RenderState> _renderStates;
        private Dictionary<int, SamplerState> _samplerStates;
        private Dictionary<int, SamplerState> _vertexSamplerStates;
        private Dictionary<String, MaterialTechnique> _techniques;

        //Error codes
        private const String ERROR_GENERAL = "X000";
        private const String ERROR_SYNTAX = "X100";
        private const String ERROR_PARENT = "X200";
        private const String ERROR_MISSING_NAME = "X300";
        private const String ERROR_FILE_NOT_FOUND = "X400";
        private const String ERROR_TYPE_MISMATCH = "X500";
        private const String ERROR_NOT_DEFINED = "X600";
        private const String ERROR_INVALID = "X700";

        //Error messages
        private const String MSG_GENERAL = "Parse error, invalid syntax";
        private const String MSG_COULD_NOT_LOAD_RESOURCE = "Could not load the resource";
        private const String MSG_FILE_NOT_FOUND = "Could not locate the file";
        private const String MSG_NO_COLON = "Missing : syntax";
        private const String MSG_OPEN_BRACE = "Missing opening { brace";
        private const String MSG_CLOSE_BRACE = "Missing closing } brace";
        private const String MSG_FLOAT = "Cannot parse value, a floating point value was expected";
        private const String MSG_INT = "Cannot parse value, an integer value was expected";
        private const String MSG_BOOL = "Cannot parse value, a boolean value was expected";
        private const String MSG_RENDERSTATE = "Missing render state declaration body";
        private const String MSG_RENDERSTATE_PROPERTY = "Invalid render state property";

        /// <summary>
        /// Creates a new instance of MaterialParser.
        /// </summary>
        /// <param name="contentManager">ContentManager to find and load secondary resources, such as effects and textures.</param>
        public MaterialParser(ContentManager contentManager) {
            _content = contentManager;
            _matParams = new Dictionary<String, MaterialParameter>();
            _engineParams = new Dictionary<String, MaterialEngineParameter>();
            _renderStates = new Dictionary<String, RenderState>();
            _samplerStates = new Dictionary<int, SamplerState>();
            _vertexSamplerStates = new Dictionary<int, SamplerState>();
            _techniques = new Dictionary<String, MaterialTechnique>();
            _cachedRenderStates = new Dictionary<RenderStateKey, RenderState>[] {
                    new Dictionary<RenderStateKey, RenderState>(),
                    new Dictionary<RenderStateKey, RenderState>(),
                    new Dictionary<RenderStateKey, RenderState>(),
                    new Dictionary<RenderStateKey, RenderState>()
                };
        }

        /// <summary>
        /// Parses a material file and loads the very first material found in the script file.
        /// </summary>
        /// <param name="resource">Resource pointing to the material script.</param>
        /// <param name="parameters">Loader parameters</param>
        /// <returns>A material</returns>
        public Material LoadFirstMaterial(IResource resource, LoaderParameters parameters) {
            ClearTransientData();
            _fullResourceName = resource.FullName;

            try {
                StreamReader strReader = new StreamReader(resource.OpenStream());
                if(_reader == null) {
                    _reader = new StringTokenizer(strReader.ReadToEnd());
                } else {
                    _reader.Read(strReader.ReadToEnd());
                }
                strReader.Close();
                strReader.Dispose();
            } catch(Exception e) {
                ThrowException(ERROR_FILE_NOT_FOUND, String.Format("Could not load TEM file: {0}", e.Message));
            }

            return ParseMaterial();
        }

        /// <summary>
        /// Parses a material file and loads the first material found in the script file that
        /// has the same matching name as in the loader parameter. If a valid material loader parameter
        /// object has not been passed, then this defaults to loading the first material in the script.
        /// </summary>
        /// <param name="resource">Resource pointing to the material script.</param>
        /// <param name="parameters">Loader parameters</param>
        /// <returns>A material</returns>
        public Material LoadMaterial(IResource resource, LoaderParameters parameters) {
            MaterialLoaderParameters matParam = parameters as MaterialLoaderParameters;
            if(matParam == null || String.IsNullOrEmpty(matParam.MaterialName)) {
                return LoadFirstMaterial(resource, parameters);
            }

            try {
                StreamReader strReader = new StreamReader(resource.OpenStream());
                if(_reader == null) {
                    _reader = new StringTokenizer(strReader.ReadToEnd());
                } else {
                    _reader.Read(strReader.ReadToEnd());
                }
                strReader.Close();
                strReader.Dispose();
            } catch(Exception e) {
                ThrowException(ERROR_FILE_NOT_FOUND, String.Format("Could not load TEM file: {0}", e.Message));
            }
            Material mat = ParseMaterial(matParam.MaterialName);
            
            if(mat == null) {
                ThrowException(ERROR_GENERAL, String.Format(String.Format("Material with name '{0}' does not exist in the TEM file.", matParam.MaterialName)));
            }
            return mat;
        }

        /// <summary>
        /// Parses a material file and loads all materials (in the order they appear in the file) as
        /// a material collection. Materials need not to have unique names but should be separated from
        /// each other at least by a whitespace or new line.
        /// </summary>
        /// <param name="resource">Resource pointing to the material script.</param>
        /// <param name="parameters">Loader parameters</param>
        /// <returns>A collection of materials</returns>
        public MaterialCollection LoadAllMaterials(IResource resource, LoaderParameters parameters) {
            ClearTransientData();
            _fullResourceName = resource.FullName;

            try {
                StreamReader strReader = new StreamReader(resource.OpenStream());
                if(_reader == null) {
                    _reader = new StringTokenizer(strReader.ReadToEnd());
                } else {
                    _reader.Read(strReader.ReadToEnd());
                }
                strReader.Close();
                strReader.Dispose();
            } catch(Exception e) {
                ThrowException(ERROR_FILE_NOT_FOUND, "Could not load TEM file. " + e.Message);
            }

            MaterialCollection matColl = new MaterialCollection(resource.Name);
            while(_reader.HasNext("Material")) {
                matColl.Add(ParseMaterial());
            }
            return matColl;
        }

        /// <summary>
        /// Clear all instance specific data.
        /// </summary>
        private void ClearTransientData() {
            _renderStates.Clear();
            _matParams.Clear();
            _engineParams.Clear();
            _techniques.Clear();
            _samplerStates.Clear();
            _vertexSamplerStates.Clear();
        }

        #region Material Parsing

        private Material ParseMaterial(String matName) {
            Material material = null;
            int braceCount = 0;

            while(_reader.HasNext()) {
                String matToken = _reader.NextToken();
                if(StringCompare(matToken, "{")) {
                    braceCount++;
                } else if(StringCompare(matToken, "}")) {
                    braceCount--;
                } else if(braceCount == 0 && StringCompare(matToken, "Material")) {
                    String name = _reader.NextToken();
                    IsValidIdentifier(name);
                    if(StringCompare(name, matName)) {
                        String nextToken = _reader.NextToken();

                        if(StringCompare(nextToken, ":")) {
                            material = ParseParentMaterial(name);
                            nextToken = _reader.NextToken();
                            if(StringCompare(nextToken, "{")) {
                                ParseSubBlocks(material);
                                ParseCloseBrace();
                            }
                        } else if(StringCompare(nextToken, "{")) {
                            material = new Material(name);
                            ParseSubBlocks(material);
                            ParseCloseBrace();
                        } else {
                            ThrowException(ERROR_SYNTAX, MSG_GENERAL);
                        }

                        //Set all the data we've accumulated, since by now we should have an effect and be ready to go
                        SetParameters(material);
                        SetEngineParameters(material);
                        SetRenderStates(material);

                        ClearTransientData();

                        return material;
                    }
                }
            }

            return material;
        }


        /// <summary>
        /// Parse a complete material.
        /// </summary>
        /// <returns></returns>
        private Material ParseMaterial() {
            Material material = null;

            String token = _reader.NextToken();

            if(StringCompare(token, "Material")) {
                String matName = _reader.NextToken();
                IsValidIdentifier(matName);

                String nextToken = _reader.NextToken();

                if(StringCompare(nextToken, ":")) {
                    material = ParseParentMaterial(matName);
                    nextToken = _reader.NextToken();
                    if(StringCompare(nextToken, "{")) {
                        ParseSubBlocks(material);
                        ParseCloseBrace();
                    } 
                } else if(StringCompare(nextToken, "{")) {
                    material = new Material(matName);
                    ParseSubBlocks(material);
                    ParseCloseBrace();
                } else {
                    ThrowException(ERROR_SYNTAX, MSG_GENERAL);
                }
            } else {
                ThrowException(ERROR_SYNTAX, String.Format("{0}, expecting Material keyword, not '{1}'.", MSG_GENERAL, token));
            }

            //Set all the data we've accumulated, since by now we should have an effect and be ready to go
            SetParameters(material);
            SetEngineParameters(material);
            SetRenderStates(material);

            ClearTransientData();

            return material;
        }

        /// <summary>
        /// Parse a parent material. The parameters and renderstates that we pick up in parsing of the child material
        /// will override any parameters of the same identifier in the parent.
        /// </summary>
        /// <returns></returns>
        private Material ParseParentMaterial(String childName) {
            String filePath = _reader.NextToken();
            String text = null;
            try {
                StreamReader strReader = new StreamReader(_content.OpenStream(filePath));
                text = strReader.ReadToEnd();
                strReader.Close();
                strReader.Dispose();
            } catch(Exception e) {
                ThrowException(ERROR_FILE_NOT_FOUND, String.Format("{0} {1}. {2}", MSG_FILE_NOT_FOUND, filePath, e.Message));
            }

            //Save our main reader/resource name
            String childResourceName = _fullResourceName;
            StringTokenizer childReader = _reader;

            _reader = new StringTokenizer(text);

            Material material = new Material(childName);

            String token = _reader.NextToken();

            if(StringCompare(token, "Material")) {
                String matName = _reader.NextToken();
                IsValidIdentifier(matName);

                String nextToken = _reader.NextToken();

                if(StringCompare(nextToken, ":")) {
                    ThrowException(ERROR_SYNTAX, "Only one level of inheritance is allowed.");
                } else if(StringCompare(nextToken, "{")) {
                    ParseSubBlocks(material);
                    ParseCloseBrace();
                } else {
                    ThrowException(ERROR_SYNTAX, MSG_GENERAL);
                }
            } else {
                ThrowException(ERROR_SYNTAX, String.Format("{0}, expecting Material keyword, not '{1}'.", MSG_GENERAL, token));
            }

            //Set all the data we've accumulated, since by now we should have an effect and be ready to go
            SetParameters(material);
            SetEngineParameters(material);
            SetRenderStates(material);

            ClearTransientData();

            //Set child stuff back
            _reader = childReader;
            _fullResourceName = childResourceName;

            return material;
        }

        /// <summary>
        /// Parse a sub block within the material.
        /// </summary>
        /// <param name="mat"></param>
        private void ParseSubBlocks(Material mat) {
            String token;

            while(HasNextStatement()) {
                token = _reader.NextToken();

                if(StringCompare(token, "Effect")) {
                    ParseEffectBlock(mat);
                } else if(StringCompare(token, "MaterialParameters")) {
                    ParseParameterBlock();
                } else if(StringCompare(token, "EngineParameters")) {
                    ParseEngineParameterBlock();
                } else if(StringCompare(token, "RenderStates")) {
                    ParseRenderStateBlock();
                } else if(StringCompare(token, "Technique")) {
                    ParseTechnique();
                } else {
                    ThrowException(ERROR_SYNTAX, String.Format("{0}, '{1}' is not a valid sub-block keyword.", MSG_GENERAL, token));
                }
            }
        }

        /// <summary>
        /// Sets all the parameters we've accumulated to the material.
        /// </summary>
        /// <param name="material"></param>
        private void SetParameters(Material material) {
            foreach(KeyValuePair<String, MaterialParameter> kv in _matParams) {
                MaterialParameter param = kv.Value;
                switch(param.ParameterType) {
                    case MaterialParameterType.Bool:
                        material.SetParameter(param.Name, param.IsSemantic, param.BoolValue);
                        break;
                    case MaterialParameterType.Int32:
                        material.SetParameter(param.Name, param.IsSemantic, param.IntValue);
                        break;
                    case MaterialParameterType.Matrix:
                        material.SetParameter(param.Name, param.IsSemantic, param.MatrixValue);
                        break;
                    case MaterialParameterType.Single:
                        material.SetParameter(param.Name, param.IsSemantic, param.SingleValue);
                        break;
                    case MaterialParameterType.Texture:
                        material.SetParameter(param.Name, param.IsSemantic, param.TextureValue);
                        break;
                    case MaterialParameterType.Vector2:
                        material.SetParameter(param.Name, param.IsSemantic, param.Vector2Value);
                        break;
                    case MaterialParameterType.Vector3:
                        material.SetParameter(param.Name, param.IsSemantic, param.Vector3Value);
                        break;
                    case MaterialParameterType.Vector4:
                        material.SetParameter(param.Name, param.IsSemantic, param.Vector4Value);
                        break;
                }
            }
        }

        /// <summary>
        /// Sets all the engine parameters we've accumulated to the material.
        /// </summary>
        /// <param name="material"></param>
        private void SetEngineParameters(Material material) {
            foreach(KeyValuePair<String, MaterialEngineParameter> kv in _engineParams) {
                MaterialEngineParameter param = kv.Value;
                material.SetEngineParameter(param.Name, param.IsSemantic, param.EngineValue);
            }
        }

        /// <summary>
        /// Sets per-pass render states in the material.
        /// </summary>
        /// <param name="material"></param>
        private void SetRenderStates(Material material) {
            //Pixel Sampler states
            MaterialSamplerStateCollection pixelSamplers = material.SamplerStates;
            foreach(KeyValuePair<int, SamplerState> kv in _samplerStates) {
                SamplerState ss = kv.Value;
                int index = kv.Key;
                if(index >= pixelSamplers.MaxSamplers) {
                    ThrowException(ERROR_NOT_DEFINED, String.Format("Invalid PixelSamplerState index. Only {0} pixel samplers supported.", pixelSamplers.MaxSamplers));
                }
                pixelSamplers[index] = ss;
            }

            //Vertex sampler states
            MaterialSamplerStateCollection vertexSamplers = material.VertexSamplerStates;
            foreach(KeyValuePair<int, SamplerState> kv in _vertexSamplerStates) {
                SamplerState ss = kv.Value;
                int index = kv.Key;
                if(index >= vertexSamplers.MaxSamplers) {
                    ThrowException(ERROR_NOT_DEFINED, String.Format("Invalid VertexSamplerState index. Only {0} vertex samplers supported.", vertexSamplers.MaxSamplers));
                }
                vertexSamplers[index] = ss;
            }

            //Per-pass render states
            foreach(KeyValuePair<String, MaterialTechnique> kv in _techniques) {
                MaterialTechnique matTechnique = kv.Value;
                Effect effect = material.Effect;
                ValidateTechnique(ref matTechnique, effect);

                RenderState bs = null;
                RenderState rs = null;
                RenderState dss = null;

                int techIndex = 0;

                for(int i = 0; i < effect.Techniques.Count; i++) {
                    IEffectTechnique tech = effect.Techniques[i];
                    if(tech.Name.Equals(matTechnique.Name)) {
                        techIndex = i;
                        break;
                    }
                }

                //Set technique render states
                if(matTechnique.HasRenderStates()) {
                    if(matTechnique.BlendStateName != null && _renderStates.TryGetValue(matTechnique.BlendStateName, out bs)) {
                        if(!(bs is BlendState)) {
                            ThrowException(ERROR_TYPE_MISMATCH, String.Format("'{0}' is not a valid BlendState", matTechnique.BlendStateName));
                        }
                    }
                    if(matTechnique.RasterizerStateName != null && _renderStates.TryGetValue(matTechnique.RasterizerStateName, out rs)) {
                        if(!(rs is RasterizerState)) {
                            ThrowException(ERROR_TYPE_MISMATCH, String.Format("'{0}' is not a valid RasterizerState", matTechnique.RasterizerStateName));
                        }
                    }
                    if(matTechnique.DepthStencilStateName != null && _renderStates.TryGetValue(matTechnique.DepthStencilStateName, out dss)) {
                        if(!(dss is DepthStencilState)) {
                            ThrowException(ERROR_TYPE_MISMATCH, String.Format("'{0}' is not a valid DepthStencilState", matTechnique.DepthStencilStateName));
                        }
                    }

                    for(int i = 0; i < material.PassCount; i++) {
                        if(bs != null) {
                            material.SetRenderState(bs, techIndex, i);
                        }

                        if(rs != null) {
                            material.SetRenderState(rs, techIndex, i);
                        }

                        if(dss != null) {
                            material.SetRenderState(dss, techIndex, i);
                        }
                    }
                }

                bs = null;
                rs = null;
                dss = null;

                //Set per pass render states, if we have any
                if(matTechnique.Passes != null) {
                    for(int i = 0; i < matTechnique.Passes.Count; i++) {
                        MaterialPass matPass = matTechnique.Passes[i];
                        String name = matPass.Name;
                        if(matPass.BlendStateName != null && _renderStates.TryGetValue(matPass.BlendStateName, out bs)) {
                            if(!(bs is BlendState)) {
                                ThrowException(ERROR_TYPE_MISMATCH, String.Format("'{0}' is not a valid BlendState", matPass.BlendStateName));
                            } else if(bs != null) {
                                material.SetRenderState(bs, techIndex, name);
                            }
                        }
                        if(matPass.RasterizerStateName != null && _renderStates.TryGetValue(matPass.RasterizerStateName, out rs)) {
                            if(!(rs is RasterizerState)) {
                                ThrowException(ERROR_TYPE_MISMATCH, String.Format("'{0}' is not a valid RasterizerState", matPass.RasterizerStateName));
                            } else if(rs != null) {
                                material.SetRenderState(rs, techIndex, name);
                            }
                        }
                        if(matPass.DepthStencilStateName != null && _renderStates.TryGetValue(matPass.DepthStencilStateName, out dss)) {
                            if(!(dss is DepthStencilState)) {
                                ThrowException(ERROR_TYPE_MISMATCH, String.Format("'{0}' is not a valid DepthStencilState", matPass.DepthStencilStateName));
                            } else if(dss != null) {
                                material.SetRenderState(dss, techIndex, name);
                            }
                        }

                        bs = null;
                        rs = null;
                        dss = null;
                    }
                }
            }
        }

        /// <summary>
        /// Validates a technique and the passes contained to ensure they exist in the effect
        /// the material uses.
        /// </summary>
        /// <param name="matTechnique"></param>
        /// <param name="effect"></param>
        private void ValidateTechnique(ref MaterialTechnique matTechnique, Effect effect) {
            if(effect == null) {
                ThrowException(ERROR_INVALID, "Material does not have a valid effect");
            }

            String techName = matTechnique.Name;

            //No passes? Then skip the check, as we may be setting render states for all passes in the effect.
            if(matTechnique.Passes == null) {
                return;
            }

            int passCount = matTechnique.Passes.Count;

            IEffectTechnique eTech = effect.Techniques[techName];

            if(eTech == null) {
                ThrowException(ERROR_NOT_DEFINED, String.Format("'{0}' no such technique defined in effect", techName));
            }

            if(passCount > eTech.Passes.Count) {
                ThrowException(ERROR_NOT_DEFINED, String.Format("Technique '{0}' contains more passes than defined in effect", techName));
            }

            foreach(MaterialPass pass in matTechnique.Passes) {
                IEffectPass ePass = eTech.Passes[pass.Name];
                if(ePass == null) {
                    ThrowException(ERROR_NOT_DEFINED, String.Format("Pass '{0}' in Technique '{1}' is not defined in effect", pass.Name, techName));
                }
            }
        }

        #endregion

        #region Effect Parsing

        /// <summary>
        /// Parses a complete Effect { } block, and loads the effect.
        /// </summary>
        /// <param name="material"></param>
        private void ParseEffectBlock(Material material) {
            ParseOpenBrace();

            String filePath = String.Empty;
            String technique = String.Empty;

            while(HasNextStatement()) {
                String token = _reader.NextToken();
                ParseSeparator();
                if(StringCompare(token, "File")) {
                    filePath = _reader.NextToken();
                } else if(StringCompare(token, "Technique")) {
                    technique = _reader.NextToken();
                } else {
                    ThrowException(ERROR_SYNTAX, MSG_GENERAL);
                }
            }

            ParseCloseBrace();

            try {
                Effect effect = _content.Load<Effect>(filePath).Clone();
                material.LoadEffect(effect);
            } catch(ResourceNotFoundException e) {
                ThrowException(ERROR_FILE_NOT_FOUND, MSG_FILE_NOT_FOUND + " ; " + e.Message);
            } catch(InvalidCastException e) {
                ThrowException(ERROR_TYPE_MISMATCH, MSG_COULD_NOT_LOAD_RESOURCE + " ; " + e.Message);
            } catch(Exception e) {
                ThrowException(ERROR_GENERAL, MSG_GENERAL + " ; " + e.Message);
            }

            if(!material.IsValid) {
                ThrowException(ERROR_INVALID, filePath + " is not a valid effect.");
            }

            if(material.Effect.Techniques[technique] == null) {
                ThrowException(ERROR_NOT_DEFINED, String.Format("Effect does not contain technique {0}", technique));
            }

            material.SetActiveTechnique(technique);
        }

        #endregion

        #region Parameter Parsing

        /// <summary>
        /// Parses a complete MaterialParameter { } block and stores each parameter
        /// in a temporary dictionary.
        /// </summary>
        private void ParseParameterBlock() {
            ParseOpenBrace();

            while(HasNextStatement()) {
                ParseParameter();
            }

            ParseCloseBrace();
        }

        /// <summary>
        /// Parses a single parameter statement, erroring if we get a token
        /// that doesn't match a recognized keword.
        /// </summary>
        private void ParseParameter() {
            String token = _reader.NextToken();

            if(StringCompare(token, "float")) {
                ParseSingle();
            } else if(StringCompare(token, "int")) {
                ParseInt();
            } else if(StringCompare(token, "bool")) {
                ParseBool();
            } else if(StringCompare(token, "Vector2")) {
                ParseVector2();
            } else if(StringCompare(token, "Vector3")) {
                ParseVector3();
            } else if(StringCompare(token, "Vector4") || StringCompare(token, "Quaternion")) {
                ParseVector4();
            } else if(StringCompare(token, "Matrix")) {
                ParseMatrix();
            } else if(StringCompare(token, "Color")) {
                ParseColor();
            } else if(StringCompare(token, "Texture1D")) {
                ParseTexture(TextureDimensions.One);
            } else if(StringCompare(token, "Texture2D")) {
                ParseTexture(TextureDimensions.Two);
            } else if(StringCompare(token, "Texture3D")) {
                ParseTexture(TextureDimensions.Three);
            } else if(StringCompare(token, "TextureCube")) {
                ParseTexture(TextureDimensions.Cube);
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a valid parameter type", token));
            }
        }

        /// <summary>
        /// Parses a single float.
        /// </summary>
        private void ParseSingle() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);
            ParseSeparator();
            try {
                MaterialParameter param = new MaterialParameter(null, paramName, false, _reader.NextSingle());
                _matParams[paramName] = param;
            } catch(Exception e) {
                ThrowException(ERROR_TYPE_MISMATCH, MSG_FLOAT + " ; " + e.Message);
            }
        }

        /// <summary>
        /// Parses a single integer.
        /// </summary>
        private void ParseInt() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);
            ParseSeparator();
            try {
                MaterialParameter param = new MaterialParameter(null, paramName, false, _reader.NextInt());
                _matParams[paramName] = param;
            } catch(Exception e) {
                ThrowException(ERROR_TYPE_MISMATCH, MSG_INT + " ; " + e.Message);
            }
        }

        /// <summary>
        /// Parses a single boolean.
        /// </summary>
        private void ParseBool() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);
            ParseSeparator();
            try {
                MaterialParameter param = new MaterialParameter(null, paramName, false, _reader.NextBool());
                _matParams[paramName] = param;
            } catch(Exception e) {
                ThrowException(ERROR_TYPE_MISMATCH, MSG_BOOL + " ; " + e.Message);
            }
        }

        /// <summary>
        /// Parses two floats, in format: x y
        /// </summary>
        private void ParseVector2() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);
            ParseSeparator();
            try {
                Vector2 value = new Vector2(_reader.NextSingle(), _reader.NextSingle());
                MaterialParameter param = new MaterialParameter(null, paramName, false, value);
                _matParams[paramName] = param;
            } catch(Exception e) {
                ThrowException(ERROR_TYPE_MISMATCH, MSG_FLOAT + " ; " + e.Message);
            }
        }

        /// <summary>
        /// Parses three floats, in format: x y z
        /// </summary>
        private void ParseVector3() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);
            ParseSeparator();
            try {
                Vector3 value = new Vector3(_reader.NextSingle(), _reader.NextSingle(), _reader.NextSingle());
                MaterialParameter param = new MaterialParameter(null, paramName, false, value);
                _matParams[paramName] = param;
            } catch(Exception e) {
                ThrowException(ERROR_TYPE_MISMATCH, MSG_FLOAT + " ; " + e.Message);
            }
        }

        /// <summary>
        /// Parses four floats, in format: x y z w
        /// </summary>
        private void ParseVector4() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);
            ParseSeparator();
            try {
                Vector4 value = new Vector4(_reader.NextSingle(), _reader.NextSingle(), _reader.NextSingle(), _reader.NextSingle());
                MaterialParameter param = new MaterialParameter(null, paramName, false, value);
                _matParams[paramName] = param;
            } catch(Exception e) {
                ThrowException(ERROR_TYPE_MISMATCH, MSG_FLOAT + " ; " + e.Message);
            }
        }

        /// <summary>
        /// Parses a color value as four byte values (integers clamped to 0-255 range), in the format: r g b a. Then are converted
        /// to floating point values in range of 0-1.0.
        /// </summary>
        private void ParseColor() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);
            ParseSeparator();
            try {
                Color value = new Color(_reader.NextInt(), _reader.NextInt(), _reader.NextInt(), _reader.NextInt());
                MaterialParameter param = new MaterialParameter(null, paramName, false, value.ToVector4());
                _matParams[paramName] = param;
            } catch(Exception e) {
                ThrowException(ERROR_TYPE_MISMATCH, MSG_INT + " ; " + e.Message);
            }
        }

        /// <summary>
        /// Parses a 4x4 row-major matrix, in format: row1 row2 row3 row4 where each row are four floats in the format x y z w
        /// </summary>
        private void ParseMatrix() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);
            ParseSeparator();
            try {
                Matrix value = new Matrix();
                for(int i = 0; i < 4; i++) {
                    switch(i) {
                        case 0:
                            value.M11 = _reader.NextSingle();
                            value.M12 = _reader.NextSingle();
                            value.M13 = _reader.NextSingle();
                            value.M14 = _reader.NextSingle();
                            break;
                        case 1:
                            value.M21 = _reader.NextSingle();
                            value.M22 = _reader.NextSingle();
                            value.M23 = _reader.NextSingle();
                            value.M24 = _reader.NextSingle();
                            break;
                        case 2:
                            value.M31 = _reader.NextSingle();
                            value.M32 = _reader.NextSingle();
                            value.M33 = _reader.NextSingle();
                            value.M34 = _reader.NextSingle();
                            break;
                        case 3:
                            value.M41 = _reader.NextSingle();
                            value.M42 = _reader.NextSingle();
                            value.M43 = _reader.NextSingle();
                            value.M44 = _reader.NextSingle();
                            break;
                    }
                }
                MaterialParameter param = new MaterialParameter(null, paramName, false, value);
                _matParams[paramName] = param;
            } catch(Exception e) {
                ThrowException(ERROR_TYPE_MISMATCH, MSG_FLOAT + " ; " + e.Message);
            }
        }

        /// <summary>
        /// Loads a texture from a parsed filepath, valid separators: / or \\ but not // since it's the start of a comment.
        /// </summary>
        /// <param name="texType"></param>
        private void ParseTexture(TextureDimensions texType) {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);
            ParseSeparator();
            try {
                String filePath = _reader.NextToken();
                MaterialParameter param;
                switch(texType) {
                    case TextureDimensions.One:
                        Texture1D tex1D = _content.Load<Texture1D>(filePath);
                        param = new MaterialParameter(null, paramName, false, tex1D);
                        _matParams[paramName] = param;
                        break;
                    case TextureDimensions.Two:
                        Texture2D tex2D = _content.Load<Texture2D>(filePath);
                        param = new MaterialParameter(null, paramName, false, tex2D);
                        _matParams[paramName] = param;
                        break;
                    case TextureDimensions.Three:
                        Texture3D tex3D = _content.Load<Texture3D>(filePath);
                        param = new MaterialParameter(null, paramName, false, tex3D);
                        _matParams[paramName] = param;
                        break;
                    case TextureDimensions.Cube:
                        TextureCube texCube = _content.Load<TextureCube>(filePath);
                        param = new MaterialParameter(null, paramName, false, texCube);
                        _matParams[paramName] = param;
                        break;
                }
            } catch(ResourceNotFoundException e) {
                ThrowException(ERROR_FILE_NOT_FOUND, MSG_FILE_NOT_FOUND + " ; " + e.Message);
            } catch(InvalidCastException e) {
                ThrowException(ERROR_TYPE_MISMATCH, MSG_COULD_NOT_LOAD_RESOURCE + " ; " + e.Message);
            } catch(Exception e) {
                ThrowException(ERROR_GENERAL, MSG_GENERAL + " ; " + e.Message);
            }
        }

        #endregion

        #region Engine Parameter Parsing

        /// <summary>
        /// Parses a complete EngineParameter { } block.
        /// </summary>
        private void ParseEngineParameterBlock() {
            ParseOpenBrace();

            while(HasNextStatement()) {
                ParseEngineParameter();
            }

            ParseCloseBrace();
        }

        /// <summary>
        /// Parses an single engine parameter statement, erroring if the next token
        /// is not a valid engine value.
        /// </summary>
        private void ParseEngineParameter() {
            String bindingName = _reader.NextToken();
            EngineValue engineValue = EngineValue.WorldMatrix;

            if(StringCompare(bindingName, "WorldMatrix")) {
                engineValue = EngineValue.WorldMatrix;
            } else if(StringCompare(bindingName, "ViewMatrix")) {
                engineValue = EngineValue.ViewMatrix;
            } else if(StringCompare(bindingName, "ProjectionMatrix")) {
                engineValue = EngineValue.ProjectionMatrix;
            } else if(StringCompare(bindingName, "WorldViewMatrix")) {
                engineValue = EngineValue.WorldViewMatrix;
            } else if(StringCompare(bindingName, "ViewProjectionMatrix")) {
                engineValue = EngineValue.ViewProjectionMatrix;
            } else if(StringCompare(bindingName, "WorldViewProjection")) {
                engineValue = EngineValue.WorldViewProjection;
            } else if(StringCompare(bindingName, "WorldInverseTranspose")) {
                engineValue = EngineValue.WorldInverseTranspose;
            } else if(StringCompare(bindingName, "WorldMatrixInverse")) {
                engineValue = EngineValue.WorldMatrixInverse;
            } else if(StringCompare(bindingName, "ViewMatrixInverse")) {
                engineValue = EngineValue.ViewMatrixInverse;
            } else if(StringCompare(bindingName, "ProjectionMatrixInverse")) {
                engineValue = EngineValue.ProjectionMatrixInverse;
            } else if(StringCompare(bindingName, "Viewport")) {
                engineValue = EngineValue.Viewport;
            } else if(StringCompare(bindingName, "Resolution")) {
                engineValue = EngineValue.Resolution;
            } else if(StringCompare(bindingName, "FrustumNearFar")) {
                engineValue = EngineValue.FrustumNearFar;
            } else if(StringCompare(bindingName, "AspectRatio")) {
                engineValue = EngineValue.AspectRatio;
            } else if(StringCompare(bindingName, "CameraPosition")) {
                engineValue = EngineValue.CameraPosition;
            } else if(StringCompare(bindingName, "CameraRight")) {
                engineValue = EngineValue.CameraRight;
            } else if(StringCompare(bindingName, "CameraUp")) {
                engineValue = EngineValue.CameraUp;
            } else if(StringCompare(bindingName, "Time")) {
                engineValue = EngineValue.Time;
            } else if(StringCompare(bindingName, "TimePerFrame")) {
                engineValue = EngineValue.TimePerFrame;
            } else if(StringCompare(bindingName, "FrameRate")) {
                engineValue = EngineValue.FrameRate;
            } else if(StringCompare(bindingName, "RandomFloat")) {
                engineValue = EngineValue.RandomFloat;
            } else if(StringCompare(bindingName, "RandomInt")) {
                engineValue = EngineValue.RandomInt;
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a valid EngineValue", bindingName));
            }

            ParseSeparator();

            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);

            MaterialEngineParameter param = new MaterialEngineParameter(null, paramName, false, engineValue);
            _engineParams[paramName] = param;
        }

        #endregion

        #region RenderState parsing

        /// <summary>
        /// Parses a complete RenderStates { } block and stores the states
        /// in a cache/temp dictionary setup, duplicates are culled.
        /// </summary>
        private void ParseRenderStateBlock() {
            ParseOpenBrace();

            while(HasNextStatement()) {
                ParseRenderState();
            }

            ParseCloseBrace();
        }

        /// <summary>
        /// Parses a single render state.
        /// </summary>
        private void ParseRenderState() {
            String token = _reader.NextToken();

            if(StringCompare(token, "BlendState")) {
                ParseBlendState();
            } else if(StringCompare(token, "RasterizerState")) {
                ParseRasterizerState();
            } else if(StringCompare(token, "DepthStencilState")) {
                ParseDepthStencilState();
            } else if(StringStartsWith(token, "SamplerState") || StringStartsWith(token, "PixelSamplerState")) {
                ParseSamplerState(false, ParseIndex(token));
            } else if(StringStartsWith(token, "VertexSamplerState")) {
                ParseSamplerState(true, ParseIndex(token));
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a valid render state", token));
            }
        }

        /// <summary>
        /// Parses a RasterizerState declaration, either a pre-defined
        /// engine state or a user-defined { } block. Properties defined
        /// in the latter correspond to run-time properties.
        /// </summary>
        private void ParseRasterizerState() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);

            String token = _reader.NextToken();

            //Parse pre-defined rasterizer states
            if(StringCompare(token, ":")) {
                String type = _reader.NextToken();
                RasterizerState rs = null;
                if(type.Equals("CullNone")) {
                    rs = RasterizerState.CullNone;
                } else if(type.Equals("CullNoneWireframe")) {
                    rs = RasterizerState.CullNoneWireframe;
                } else if(type.Equals("CullBackClockwiseFront")) {
                    rs = RasterizerState.CullBackClockwiseFront;
                } else if(type.Equals("CullBackCounterClockwiseFront")) {
                    rs = RasterizerState.CullBackCounterClockwiseFront;
                } else {
                    ThrowException(ERROR_NOT_DEFINED, String.Format("'{0}' is not a valid pre-defined RasterizerState.", type));
                }
                _renderStates[paramName] = rs;
            //Parse user-defined rasterizer states
            } else if(StringCompare(token, "{")) {
                RasterizerState rs = new RasterizerState();
                while(HasNextStatement()) {
                    ParseRasterizerStateProperty(rs);
                }
                ParseCloseBrace();
                _renderStates[paramName] = CheckExistingState(rs);
            } else {
                ThrowException(ERROR_SYNTAX, MSG_RENDERSTATE);
            }
        }

        /// <summary>
        /// Parses a single rasterizerstate property in a user-defined { } block.
        /// </summary>
        /// <param name="rs"></param>
        private void ParseRasterizerStateProperty(RasterizerState rs) {
            String property = _reader.NextToken();

            if(StringCompare(property, "Cull")) {
                ParseSeparator();
                String value = _reader.NextToken();
                if(StringCompare(value, "Back")) {
                    rs.Cull = CullMode.Back;
                } else if(StringCompare(value, "Front")) {
                    rs.Cull = CullMode.Front;
                } else if(StringCompare(value, "None")) {
                    rs.Cull = CullMode.None;
                } else {
                    ThrowException(ERROR_INVALID, String.Format("{0} '{1}'", MSG_RENDERSTATE_PROPERTY, value));
                }
            } else if(StringCompare(property, "VertexWinding")) {
                ParseSeparator();
                String value = _reader.NextToken();
                if(StringCompare(value, "Clockwise")) {
                    rs.VertexWinding = VertexWinding.Clockwise;
                } else if(StringCompare(value, "CounterClockwise")) {
                    rs.VertexWinding = VertexWinding.CounterClockwise;
                } else {
                    ThrowException(ERROR_INVALID, String.Format("{0} '{1}'", MSG_RENDERSTATE_PROPERTY, value));
                }
            } else if(StringCompare(property, "DepthBias")) {
                ParseSeparator();
                rs.DepthBias = _reader.NextInt();
            } else if(StringCompare(property, "Fill")) {
                ParseSeparator();
                String value = _reader.NextToken();
                if(StringCompare(value, "Solid")) {
                    rs.Fill = FillMode.Solid;
                } else if(StringCompare(value, "WireFrame")) {
                    rs.Fill = FillMode.WireFrame;
                } else {
                    ThrowException(ERROR_INVALID, String.Format("{0} '{1}'", MSG_RENDERSTATE_PROPERTY, value));
                }
            } else if(StringCompare(property, "EnableMultiSampleAntiAlias")) {
                ParseSeparator();
                rs.EnableMultiSampleAntiAlias = _reader.NextBool();
            } else if(StringCompare(property, "EnableScissorTest")) {
                ParseSeparator();
                rs.EnableScissorTest = _reader.NextBool();
            } else if(StringCompare(property, "SlopeScaledDepthBias")) {
                ParseSeparator();
                rs.SlopeScaledDepthBias = _reader.NextSingle();
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a valid RasterizerState property.", property));
            }
        }

        /// <summary>
        /// Parses a DepthStencilState declaration, either a pre-defined
        /// engine state or a user-defined { } block. Properties defined
        /// in the latter correspond to run-time properties.
        /// </summary>
        private void ParseDepthStencilState() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);

            String token = _reader.NextToken();

            //Parse pre-defined depth stencil state
            if(StringCompare(token, ":")) {
                String type = _reader.NextToken();
                DepthStencilState dss = null;
                if(type.Equals("Default")) {
                    dss = DepthStencilState.Default;
                } else if(type.Equals("DepthWriteOff")) {
                    dss = DepthStencilState.DepthWriteOff;
                } else if(type.Equals("None")) {
                    dss = DepthStencilState.None;
                } else {
                    ThrowException(ERROR_NOT_DEFINED, String.Format("'{0}' is not a valid pre-defined DepthStencilState.", type));
                }
                _renderStates[paramName] = dss;
            //Parse user-defined depth stencil state
            } else if(StringCompare(token, "{")) {
                DepthStencilState dss = new DepthStencilState();
                while(HasNextStatement()) {
                    ParseDepthStencilStateProperty(dss);
                }
                ParseCloseBrace();
                _renderStates[paramName] = CheckExistingState(dss);
            } else {
                ThrowException(ERROR_SYNTAX, MSG_RENDERSTATE);
            }
        }

        /// <summary>
        /// Parses a single DepthStencilState property in a user-defined { } block.
        /// </summary>
        /// <param name="dss"></param>
        private void ParseDepthStencilStateProperty(DepthStencilState dss) {
            String property = _reader.NextToken();

            if(StringCompare(property, "DepthEnable")) {
                ParseSeparator();
                dss.DepthEnable = _reader.NextBool();
            } else if(StringCompare(property, "DepthWriteEnable")) {
                ParseSeparator();
                dss.DepthEnable = _reader.NextBool();
            } else if(StringCompare(property, "DepthFunction")) {
                ParseSeparator();
                dss.DepthFunction = ParseComparisonFunction();
            } else if(StringCompare(property, "StencilEnable")) {
                ParseSeparator();
                dss.StencilEnable = _reader.NextBool();
            } else if(StringCompare(property, "ReferenceStencil")) {
                ParseSeparator();
                dss.ReferenceStencil = _reader.NextInt();
            } else if(StringCompare(property, "CCWStencilFunction") || StringCompare(property, "CounterClockwiseStencilFunction")) {
                ParseSeparator();
                dss.CounterClockwiseStencilFunction = ParseComparisonFunction();
            } else if(StringCompare(property, "CCWStencilDepthFail") || StringCompare(property, "CounterClockwiseStencilDepthFail")) {
                ParseSeparator();
                dss.CounterClockwiseStencilDepthFail = ParseStencilOperation();
            } else if(StringCompare(property, "CCWStencilFail") || StringCompare(property, "CounterClockwiseStencilFail")) {
                ParseSeparator();
                dss.CounterClockwiseStencilFail = ParseStencilOperation();
            } else if(StringCompare(property, "CCWStencilPass") || StringCompare(property, "CounterClockwiseStencilPass")) {
                ParseSeparator();
                dss.CounterClockwiseStencilPass = ParseStencilOperation();
            } else if(StringCompare(property, "StencilFunction")) {
                ParseSeparator();
                dss.StencilFunction = ParseComparisonFunction();
            } else if(StringCompare(property, "StencilDepthFail")) {
                ParseSeparator();
                dss.StencilDepthFail = ParseStencilOperation();
            } else if(StringCompare(property, "StencilFail")) {
                ParseSeparator();
                dss.StencilFail = ParseStencilOperation();
            } else if(StringCompare(property, "StencilPass")) {
                ParseSeparator();
                dss.StencilPass = ParseStencilOperation();
            } else if(StringCompare(property, "TwoSidedStencilEnable")) {
                ParseSeparator();
                dss.TwoSidedStencilEnable = _reader.NextBool();
            } else if(StringCompare(property, "StencilReadMask")) {
                ParseSeparator();
                dss.StencilReadMask = _reader.NextInt();
            } else if(StringCompare(property, "StencilWriteMask")) {
                ParseSeparator();
                dss.StencilWriteMask = _reader.NextInt();
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a valid DepthStencilState property.", property));
            }
        }

        /// <summary>
        /// Parses a sampler state description - either a pre-defined engine state or a user-defined description
        /// </summary>
        /// <param name="vertexSampler"></param>
        /// <param name="index"></param>
        private void ParseSamplerState(bool vertexSampler, int index) {
            String token = _reader.NextToken();

            //Parse pre-defined sampler state
            if(StringCompare(token, ":")) {
                String type = _reader.NextToken();
                SamplerState ss = null;
                if(type.Equals("AnisotropicClamp")) {
                    ss = SamplerState.AnisotropicClamp;
                } else if(type.Equals("AnisotropicWrap")) {
                    ss = SamplerState.AnisotropicWrap;
                } else if(type.Equals("LinearClamp")) {
                    ss = SamplerState.LinearClamp;
                } else if(type.Equals("LinearWrap")) {
                    ss = SamplerState.LinearWrap;
                } else if(type.Equals("PointClamp")) {
                    ss = SamplerState.PointClamp;
                } else if(type.Equals("PointWrap")) {
                    ss = SamplerState.PointWrap;
                } else {
                    ThrowException(ERROR_NOT_DEFINED, String.Format("'{0}' is not a valid pre-defined SamplerState.", type));
                }
                if(vertexSampler) {
                    _vertexSamplerStates[index] = ss;
                } else {
                    _samplerStates[index] = ss;
                }
                //Parse user-defined sampler state
            } else if(StringCompare(token, "{")) {
                SamplerState ss = new SamplerState();
                while(HasNextStatement()) {
                    ParseSamplerStateProperty(ss);
                }
                ParseCloseBrace();
                if(vertexSampler) {
                    _vertexSamplerStates[index] = (SamplerState) CheckExistingState(ss);
                } else {
                    _samplerStates[index] = (SamplerState) CheckExistingState(ss);
                }
            } else {
                ThrowException(ERROR_SYNTAX, MSG_RENDERSTATE);
            }
        }

        /// <summary>
        /// Parses a single sampler state property.
        /// </summary>
        /// <param name="ss"></param>
        private void ParseSamplerStateProperty(SamplerState ss) {
            String property = _reader.NextToken();

            if(StringCompare(property, "AddressU")) {
                ParseSeparator();
                ss.AddressU = ParseAddressMode();
            } else if(StringCompare(property, "AddressV")) {
                ParseSeparator();
                ss.AddressV = ParseAddressMode();
            } else if(StringCompare(property, "AddressW")) {
                ParseSeparator();
                ss.AddressW = ParseAddressMode();
            } else if(StringCompare(property, "Filter")) {
                ParseSeparator();
                ss.Filter = ParseTextureFilter();
            } else if(StringCompare(property, "MaxAnisotropy")) {
                ParseSeparator();
                ss.MaxAnisotropy = _reader.NextInt();
            } else if(StringCompare(property, "MipMapLevelOfDetailBias") || StringCompare(property, "MipMapLoDBias")) {
                ParseSeparator();
                ss.MipMapLevelOfDetailBias = _reader.NextSingle();
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a valid SamplerState property.", property));
            }
        }

        /// <summary>
        /// Parses a blend state description - either a pre-defined engine state or a user-defined description.
        /// </summary>
        private void ParseBlendState() {
            String paramName = _reader.NextToken();
            IsValidIdentifier(paramName);

            String token = _reader.NextToken();

            //Parse pre-defined blend state
            if(StringCompare(token, ":")) {
                String type = _reader.NextToken();
                BlendState bs = null;
                if(type.Equals("AdditiveBlend")) {
                    bs = BlendState.AdditiveBlend;
                } else if(type.Equals("Opaque")) {
                    bs = BlendState.Opaque;
                } else if(type.Equals("AlphaBlendNonPremultiplied")) {
                    bs = BlendState.AlphaBlendNonPremultiplied;
                } else if(type.Equals("AlphaBlendPremultiplied")) {
                    bs = BlendState.AlphaBlendPremultiplied;
                } else {
                    ThrowException(ERROR_NOT_DEFINED, String.Format("'{0}' is not a valid pre-defined SamplerState.", type));
                }
                _renderStates[paramName] = bs;
                //Parse user-defined blend state
            } else if(StringCompare(token, "{")) {
                BlendState bs = new BlendState();
                while(HasNextStatement()) {
                    ParseBlendStateProperty(bs);
                }
                ParseCloseBrace();
                _renderStates[paramName] = CheckExistingState(bs);
            } else {
                ThrowException(ERROR_SYNTAX, MSG_RENDERSTATE);
            }
        }

        /// <summary>
        /// Parses a single blend state property
        /// </summary>
        /// <param name="bs"></param>
        private void ParseBlendStateProperty(BlendState bs) {
            String property = _reader.NextToken();

            if(StringCompare(property, "AlphaBlendFunction")) {
                ParseSeparator();
                bs.AlphaBlendFunction = ParseBlendFunction();
            } else if(StringCompare(property, "AlphaSourceBlend")) {
                ParseSeparator();
                bs.AlphaSourceBlend = ParseBlend();
            } else if(StringCompare(property, "AlphaDestinationBlend")) {
                ParseSeparator();
                bs.AlphaDestinationBlend = ParseBlend();
            } else if(StringCompare(property, "ColorBlendFunction")) {
                ParseSeparator();
                bs.ColorBlendFunction = ParseBlendFunction();
            } else if(StringCompare(property, "ColorSourceBlend")) {
                ParseSeparator();
                bs.ColorSourceBlend = ParseBlend();
            } else if(StringCompare(property, "ColorDestinationBlend")) {
                ParseSeparator();
                bs.ColorDestinationBlend = ParseBlend();
            } else if(StringCompare(property, "BlendFactor")) {
                ParseSeparator();
                bs.BlendFactor = new Color(_reader.NextInt(), _reader.NextInt(), _reader.NextInt(), _reader.NextInt());
            } else if(StringCompare(property, "MultiSampleMask")) {
                ParseSeparator();
                bs.MultiSampleMask = _reader.NextInt();
            } else if(StringStartsWith(property, "ColorWriteChannels")) {
                ParseSeparator();
                int index = ParseIndex(property);
                ColorWriteChannels channels = ParseColorWriteChannels();

                while(_reader.HasNext("|")) {
                    _reader.NextToken();
                    channels |= ParseColorWriteChannels();
                }

                if(index < bs.ColorWriteChannelsCount) {
                    bs.SetWriteChannels(index, channels);
                }
            } else if(StringStartsWith(property, "BlendEnable")) {
                ParseSeparator();
                int index = ParseIndex(property);
                bool value = _reader.NextBool();
                if(index < bs.BlendEnableCount) {
                    bs.SetBlendEnable(index, value);
                }
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a valid BlendState property.", property));
            }
        }

        #endregion

        #region Technique Parsing

        /// <summary>
        /// Parses a technique { } block, which has a name and potentially Blend, Rasterizer, and DepthStencilStates. A technique may also be
        /// made up of a series of passes. Render states set at the technique level will be applied for each pass, including passes
        /// not referenced in the material file but are present in the shader, unless if a pass defines a render state of the same type.
        /// </summary>
        private void ParseTechnique() {
            String techName = _reader.NextToken();
            IsValidIdentifier(techName);

            ParseOpenBrace();

            String token;

            MaterialTechnique technique = new MaterialTechnique();
            technique.Name = techName;

            while(HasNextStatement()) {
                token = _reader.NextToken();

                if(StringCompare(token, "Pass")) {
                    if(technique.Passes == null) {
                        technique.Passes = new List<MaterialPass>(1);
                    }
                    MaterialPass matPass;
                    ParsePass(out matPass);
                    technique.Passes.Add(matPass);
                } else if(StringCompare(token, "BlendState")) {
                    ParseSeparator();
                    String bsName = _reader.NextToken();
                    IsValidIdentifier(bsName);
                    technique.BlendStateName = bsName;
                } else if(StringCompare(token, "RasterizerState")) {
                    ParseSeparator();
                    String rsName = _reader.NextToken();
                    IsValidIdentifier(rsName);
                    technique.RasterizerStateName = rsName;
                } else if(StringCompare(token, "DepthStencilState")) {
                    ParseSeparator();
                    String dssName = _reader.NextToken();
                    IsValidIdentifier(dssName);
                    technique.DepthStencilStateName = dssName;
                } else {
                    ThrowException(ERROR_SYNTAX, String.Format("{0}, '{1}' is not a valid technique property.", MSG_GENERAL, token));
                }
            }

            ParseCloseBrace();

            _techniques[techName] = technique;
        }

        /// <summary>
        /// Parses a pass { } block inside a technique, passes have a name and potentially a Blend,Rasterizer, and DepthStencil state.
        /// </summary>
        /// <returns></returns>
        private void ParsePass(out MaterialPass pass) {
            String passName = _reader.NextToken();
            IsValidIdentifier(passName);

            ParseOpenBrace();

            String token;

            pass = new MaterialPass();
            pass.Name = passName;

            while(HasNextStatement()) {
                token = _reader.NextToken();

                if(StringCompare(token, "BlendState")) {
                    ParseSeparator();
                    String bsName = _reader.NextToken();
                    IsValidIdentifier(bsName);
                    pass.BlendStateName = bsName;
                } else if(StringCompare(token, "RasterizerState")) {
                    ParseSeparator();
                    String rsName = _reader.NextToken();
                    IsValidIdentifier(rsName);
                    pass.RasterizerStateName = rsName;
                } else if(StringCompare(token, "DepthStencilState")) {
                    ParseSeparator();
                    String dssName = _reader.NextToken();
                    IsValidIdentifier(dssName);
                    pass.DepthStencilStateName = dssName;
                } else {
                    ThrowException(ERROR_SYNTAX, String.Format("{0}, '{1}' is not a valid pass property.", MSG_GENERAL, token));
                }
            }

            ParseCloseBrace();
        }

        #endregion

        #region Common

        /// <summary>
        /// Parses : statement syntax, erroring if the token is not equivalent.
        /// </summary>
        private void ParseSeparator() {
            if(!_reader.NextToken().Equals(":")) {
                ThrowException(ERROR_SYNTAX, MSG_NO_COLON);
            }
        }

        /// <summary>
        /// Parses { block syntax, erroring if the token is not equivalent.
        /// </summary>
        private void ParseOpenBrace() {
            if(!_reader.NextToken().Equals("{")) {
                ThrowException(ERROR_SYNTAX, MSG_OPEN_BRACE);
            }
        }

        /// <summary>
        /// Parses } block syntax, erroring if the token is not equivalent.
        /// </summary>
        private void ParseCloseBrace() {
            if(!_reader.NextToken().Equals("}")) {
                ThrowException(ERROR_SYNTAX, MSG_CLOSE_BRACE);
            }
        }

        /// <summary>
        /// Checks if we have a next statement in a { } block, which is a simple check to see
        /// if we have hit the closing curly brace.
        /// </summary>
        /// <returns></returns>
        private bool HasNextStatement() {
            return !_reader.HasNext("}");
        }

        /// <summary>
        /// String comparison helper that uses StringComparison.InvariantCultureIgnoreCase
        /// for comparing tokens to keywords/syntax. Keywords are not case sensitive.
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        private bool StringCompare(String left, String right) {
            return left.Equals(right, StringComparison.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// String comparison helper that uses StringComparison.InvariantCultureIgnoreCase
        /// to see if the left string starts with the string sequence defined by the right string.
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <returns></returns>
        private bool StringStartsWith(String left, String right) {
            return left.StartsWith(right, StringComparison.InvariantCultureIgnoreCase);
        }

        /// <summary>
        /// Exception helper that throws an IOException with an error code, error message,
        /// and other details such as the resource name, line number and column number approximately where the error occured.
        /// </summary>
        /// <param name="errorCode"></param>
        /// <param name="errorMsg"></param>
        private void ThrowException(String errorCode, String errorMsg) {
            if(_reader == null) {
                throw new IOException(String.Format("{0} error {3} : {4}", _fullResourceName, errorCode, errorMsg));
            } else {
                throw new IOException(String.Format("{0} ({1}, {2}) error {3} : {4}", _fullResourceName, _reader.LineNumber, _reader.Column, errorCode, errorMsg));
            }
        }

        /// <summary>
        /// Checks if an identifier is valid.
        /// </summary>
        /// <param name="name"></param>
        private void IsValidIdentifier(String name) {
            //Currently only check if an identifier is a curly brace or a colon, as we sometimes use those to coordinate our parsing, so it's
            //a bad idea to let identifiers use them. Maybe we'll be strict with keywords, but for now just these three.
            if(name.Equals("{", StringComparison.InvariantCultureIgnoreCase) || name.Equals("}", StringComparison.InvariantCultureIgnoreCase)
                || name.Equals(":", StringComparison.InvariantCultureIgnoreCase)) {
                    ThrowException(ERROR_INVALID, String.Format("'{0}' identifier is invalid as it conflicts with a keyword or syntax symbol."));
            }
        }

        /// <summary>
        /// Checks if a render state already exists in our cache, if it does we return the old state. If it doesn't we add it and
        /// bind the state, to create the underlying graphics implementation.
        /// </summary>
        /// <param name="state"></param>
        /// <returns></returns>
        private RenderState CheckExistingState(RenderState state) {
            Dictionary<RenderStateKey, RenderState> cache = _cachedRenderStates[(int)state.StateType];
            RenderState existingState;
            if(cache.TryGetValue(state.RenderStateKey, out existingState)) {
                return existingState;
            } else {
                cache.Add(state.RenderStateKey, state);
                state.BindRenderState();
                return state;
            }
        }

        /// <summary>
        /// Parses a ComparisonFunction enum.
        /// </summary>
        /// <returns></returns>
        private ComparisonFunction ParseComparisonFunction() {
            String compFunc = _reader.NextToken();

            if(StringCompare(compFunc, "Always")) {
                return ComparisonFunction.Always;
            } else if(StringCompare(compFunc, "Never")) {
                return ComparisonFunction.Never;
            } else if(StringCompare(compFunc, "Less")) {
                return ComparisonFunction.Less;
            } else if(StringCompare(compFunc, "LessEqual")) {
                return ComparisonFunction.LessEqual;
            } else if(StringCompare(compFunc, "Equal")) {
                return ComparisonFunction.Equal;
            } else if(StringCompare(compFunc, "GreaterEqual")) {
                return ComparisonFunction.GreaterEqual;
            } else if(StringCompare(compFunc, "Greater")) {
                return ComparisonFunction.Greater;
            } else if(StringCompare(compFunc, "NotEqual")) {
                return ComparisonFunction.NotEqual;
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a member of the ComparisonFunction enum", compFunc));
            }
            return ComparisonFunction.Always;
        }

        /// <summary>
        /// Parses a StencilOperation enum.
        /// </summary>
        /// <returns></returns>
        private StencilOperation ParseStencilOperation() {
            String stencilOp = _reader.NextToken();

            if(StringCompare(stencilOp, "Keep")) {
                return StencilOperation.Keep;
            } else if(StringCompare(stencilOp, "Zero")) {
                return StencilOperation.Zero;
            } else if(StringCompare(stencilOp, "Replace")) {
                return StencilOperation.Replace;
            } else if(StringCompare(stencilOp, "Increment")) {
                return StencilOperation.Increment;
            } else if(StringCompare(stencilOp, "Decrement")) {
                return StencilOperation.Decrement;
            } else if(StringCompare(stencilOp, "IncrementAndClamp")) {
                return StencilOperation.IncrementAndClamp;
            } else if(StringCompare(stencilOp, "DecrementAndClamp")) {
                return StencilOperation.DecrementAndClamp;
            } else if(StringCompare(stencilOp, "Invert")) {
                return StencilOperation.Invert;
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a member of the StencilOperation enum", stencilOp));
            }

            return StencilOperation.Keep;
        }

        /// <summary>
        /// Parses a TextureFilter enum.
        /// </summary>
        /// <returns></returns>
        private TextureFilter ParseTextureFilter() {
            String filter = _reader.NextToken();

            if(StringCompare(filter, "Point")) {
                return TextureFilter.Point;
            } else if(StringCompare(filter, "PointMipLinear")) {
                return TextureFilter.PointMipLinear;
            } else if(StringCompare(filter, "Linear")) {
                return TextureFilter.Linear;
            } else if(StringCompare(filter, "LinearMipPoint")) {
                return TextureFilter.LinearMipPoint;
            } else if(StringCompare(filter, "MinLinearMagPointMipLinear")) {
                return TextureFilter.MinLinearMagPointMipLinear;
            } else if(StringCompare(filter, "MinLinearMagPointMipPoint")) {
                return TextureFilter.MinLinearMagPointMipPoint;
            } else if(StringCompare(filter, "MinPointMagLinearMipLinear")) {
                return TextureFilter.MinPointMagLinearMipLinear;
            } else if(StringCompare(filter, "MinPointMagLinearMipPoint")) {
                return TextureFilter.MinPointMagLinearMipPoint;
            } else if(StringCompare(filter, "Anisotropic")) {
                return TextureFilter.Anisotropic;
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a member of the TextureFilter enum", filter));
            }

            return TextureFilter.Point;
        }

        /// <summary>
        /// Parses a TextureAddressMode enum.
        /// </summary>
        /// <returns></returns>
        private TextureAddressMode ParseAddressMode() {
            String address = _reader.NextToken();

            if(StringCompare(address, "Clamp")) {
                return TextureAddressMode.Clamp;
            } else if(StringCompare(address, "Wrap")) {
                return TextureAddressMode.Wrap;
            } else if(StringCompare(address, "Mirror")) {
                return TextureAddressMode.Mirror;
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a member of the TextureAddressMode enum", address));
            }

            return TextureAddressMode.Clamp;
        }

        /// <summary>
        /// Parses a ColorWriteChannel bitflag enum.
        /// </summary>
        /// <returns></returns>
        private ColorWriteChannels ParseColorWriteChannels() {
            String channel = _reader.NextToken();

            if(StringCompare(channel, "None")) {
                return ColorWriteChannels.None;
            } else if(StringCompare(channel, "Red")) {
                return ColorWriteChannels.Red;
            } else if(StringCompare(channel, "Green")) {
                return ColorWriteChannels.Green;
            } else if(StringCompare(channel, "Blue")) {
                return ColorWriteChannels.Blue;
            } else if(StringCompare(channel, "Alpha")) {
                return ColorWriteChannels.Alpha;
            } else if(StringCompare(channel, "All")) {
                return ColorWriteChannels.All;
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a member of the ColorWriteChannels bitflag enum", channel));
            }
            return ColorWriteChannels.All;
        }

        /// <summary>
        /// Parses a BlendFunction enum.
        /// </summary>
        /// <returns></returns>
        private BlendFunction ParseBlendFunction() {
            String func = _reader.NextToken();

            if(StringCompare(func, "Add")) {
                return BlendFunction.Add;
            } else if(StringCompare(func, "Subtract")) {
                return BlendFunction.Subtract;
            } else if(StringCompare(func, "ReverseSubtract")) {
                return BlendFunction.ReverseSubtract;
            } else if(StringCompare(func, "Min")) {
                return BlendFunction.Min;
            } else if(StringCompare(func, "Max")) {
                return BlendFunction.Max;
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a member of the BlendFunction enum", func));
            }

            return BlendFunction.Add;
        }

        /// <summary>
        /// Parses a Blend enum.
        /// </summary>
        /// <returns></returns>
        private Blend ParseBlend() {
            String blend = _reader.NextToken();

            if(StringCompare(blend, "Zero")) {
                return Blend.Zero;
            } else if(StringCompare(blend, "One")) {
                return Blend.One;
            } else if(StringCompare(blend, "SourceColor")) {
                return Blend.SourceColor;
            } else if(StringCompare(blend, "InverseSourceColor")) {
                return Blend.InverseSourceColor;
            } else if(StringCompare(blend, "SourceAlpha")) {
                return Blend.SourceAlpha;
            } else if(StringCompare(blend, "InverseSourceAlpha")) {
                return Blend.InverseSourceAlpha;
            } else if(StringCompare(blend, "DestinationColor")) {
                return Blend.DestinationColor;
            } else if(StringCompare(blend, "InverseDestinationColor")) {
                return Blend.InverseDestinationColor;
            } else if(StringCompare(blend, "DestinationAlpha")) {
                return Blend.DestinationAlpha;
            } else if(StringCompare(blend, "InverseDestinationAlpha")) {
                return Blend.InverseDestinationAlpha;
            } else if(StringCompare(blend, "BlendFactor")) {
                return Blend.BlendFactor;
            } else if(StringCompare(blend, "InverseBlendFactor")) {
                return Blend.InverseBlendFactor;
            } else if(StringCompare(blend, "SourceAlphaSaturation")) {
                return Blend.SourceAlphaSaturation;
            } else {
                ThrowException(ERROR_INVALID, String.Format("'{0}' is not a member of the Blend enum", blend));
            }

            return Blend.One;
        }

        /// <summary>
        /// Parses an integer index value from a string with syntax: value[index], e.g. SamplerState[0] or BlendEnable[1]
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private int ParseIndex(String name) {
            //Keep it simple, any pre-fixes that have any erronous brackets will be picked up before we reach this point
            int i = name.IndexOf("[");
            int j = name.LastIndexOf("]");
            String num = name.Substring(i + 1, (j - 1) - i);
            int result;
            if(int.TryParse(num, out result)) {
                return result;
            }
            return 0;
        }

        #endregion

        #region Structures

        private struct MaterialTechnique {
            public String Name;
            public List<MaterialPass> Passes;
            public String BlendStateName;
            public String RasterizerStateName;
            public String DepthStencilStateName;

            public bool HasRenderStates() {
                return BlendStateName != null || RasterizerStateName != null || DepthStencilStateName != null;
            }
        }

        private struct MaterialPass {
            public String Name;
            public String BlendStateName;
            public String RasterizerStateName;
            public String DepthStencilStateName;

            public bool HasRenderStates() {
                return BlendStateName != null || RasterizerStateName != null || DepthStencilStateName != null;
            }
        }

        #endregion
    }
}
